Skip to content

fix(react-breadcrumb): make BreadcrumbButtonBaseProps distribute over the ARIA button union - #36663

Merged
Dmytro Kirpa (dmytrokirpa) merged 6 commits into
microsoft:masterfrom
ArrayKnight:fix/breadcrumb-distributive-omit-36645
Sep 3, 2026
Merged

fix(react-breadcrumb): make BreadcrumbButtonBaseProps distribute over the ARIA button union#36663
Dmytro Kirpa (dmytrokirpa) merged 6 commits into
microsoft:masterfrom
ArrayKnight:fix/breadcrumb-distributive-omit-36645

Conversation

@ArrayKnight

@ArrayKnight Ray Knight (ArrayKnight) commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

BreadcrumbButtonBaseProps is declared as a plain Omit<BreadcrumbButtonProps, 'size'>. BreadcrumbButtonProps includes ComponentProps<ButtonSlots>, whose root is ARIAButtonSlotProps — a distributive union over as: 'button' | 'a'. A plain Omit collapses that union into a single object type keyed on the intersection of its members, so the anchor arm's href disappears and the anchor spelling of BreadcrumbButton (the one the component's own Default story uses) no longer type-checks against the base props type.

The fix switches to DistributiveOmit, matching what @fluentui/react-button already does for the same reason. Type-level only — no runtime change — and the widened type is strictly a superset of the current one, so no existing consumer code stops compiling. The etc/react-breadcrumb.api.md report is regenerated to match.

Fixes #36645.

Extracted from #36656 per maintainer request — each in-tree fix from that PR as an isolated change.


Also fixes #36681: while adding the requested base-hook test, we found that controlType's as ?? href ? 'a' : 'button' parses as (as ?? href) ? 'a' : 'button', so an explicit as: 'button' (truthy) computed 'a' and the ARIA button pipeline emitted the anchor arm (<a role="button">). Parenthesized so an explicit as short-circuits the href inference, with a red/green renderHook regression test alongside the existing anchor-arm test.

… the ARIA button union

`BreadcrumbButtonBaseProps` was declared with a plain `Omit`:

    export type BreadcrumbButtonBaseProps = Omit<BreadcrumbButtonProps, 'size'>;

`BreadcrumbButtonProps` includes `ComponentProps<ButtonSlots>`, whose `root`
slot is `ARIAButtonSlotProps<'a'>` -- a union over `{ as?: 'button' } & button
attrs` and `{ as: 'a' } & anchor attrs`. Plain `Omit` is `Pick<T, Exclude<keyof
T, K>>`, and `keyof` a union keeps only the keys common to every member, so the
omit collapses the union and every anchor-only prop (`href`, `target`, `rel`,
...) disappears from the derived type. `@fluentui/react-button` avoids exactly
this on the same shape by using `DistributiveOmit`
(Button.types.ts:72,84); this makes `react-breadcrumb` consistent with it.

Healed -- all three now type-check against the base surface where none did
before:

  <BreadcrumbButton href="#a">              (the spelling react-breadcrumb's
                                             own Default story uses)
  <BreadcrumbButton as="a" href="#a">
  <BreadcrumbButton as="a" href target rel>

Not changed, and honestly not a regression: a props object literal whose only
property is `href` is still rejected. That is TypeScript weak-type detection --
the `{ as?: 'button' }` union member has no required properties and shares no
property with `{ href }`, so the source only matches the `a` member, which then
demands an explicit `as: 'a'`. It fires identically on the Griffel
`BreadcrumbButtonProps`, and adding any shared property (`children`, which every
real JSX usage has) satisfies it on both. Runtime behaviour is unchanged: this
file declares types only and emits nothing.

etc/react-breadcrumb.api.md regenerated by the build.

Verified: react-breadcrumb type-check + lint pass; react-headless-components-preview
and react-components type-check pass. react-breadcrumb:test is 2 failed / 105
passed both with and without this change (pre-existing @fluentui/react-icons
snapshot drift -- SVG path data and the `fui-Icon` class -- unrelated to it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj9uA3rCVgosnh2zNn8qkc
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-breadcrumb
@fluentui/react-breadcrumb - package
103.669 kB
29.081 kB
103.667 kB
29.079 kB
-2 B
-2 B
react-components
react-components: all base hooks
217.425 kB
68.187 kB
217.423 kB
68.185 kB
-2 B
-2 B
react-components
react-components: entire library
1.283 MB
322.114 kB
1.283 MB
322.112 kB
-2 B
-2 B
react-headless-components-preview
react-headless-components-preview: entire library
240.517 kB
67.831 kB
240.515 kB
67.828 kB
-2 B
-3 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Button, FluentProvider & webLightTheme
67.471 kB
19.465 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
227.136 kB
68.41 kB
react-components
react-components: FluentProvider & webLightTheme
40.694 kB
13.549 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/tag-picker
54.012 kB
17.756 kB
react-headless-components-preview
@fluentui/react-headless-components-preview/teaching-popover
36.073 kB
12.006 kB
react-portal-compat
PortalCompatProvider
5.341 kB
2.146 kB
react-timepicker-compat
TimePicker
142.037 kB
46.44 kB
🤖 This report was generated against 5c311a6ab91ec1a0482d1fe83b52d8e308a10d2e

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Pull request demo site: URL

…Type ternary (microsoft#36681)

`as ?? href ? 'a' : 'button'` parses as `(as ?? href) ? 'a' : 'button'`,
so an explicit `as: 'button'` (truthy) computed controlType 'a' and the
ARIA button pipeline emitted the anchor arm (as: 'a', role="button") for
a caller who asked for a real <button>. Parenthesize so an explicit `as`
short-circuits the href inference, and add a red/green renderHook
regression test alongside the existing anchor-arm test.
@ArrayKnight

Copy link
Copy Markdown
Contributor Author

Dmytro Kirpa (@dmytrokirpa) While adding the base-hook test you asked for, we hit an operator-precedence bug in the same hook: controlType's as ?? href ? 'a' : 'button' parses as (as ?? href) ? 'a' : 'button', so an explicit as: 'button' computed 'a' and rendered the anchor arm (<a role="button">). Filed #36681 and fixed it here in the same line the PR already touches conceptually, with a red/green renderHook regression test — happy to split it out into a separate PR if you'd prefer it isolated.

@dmytrokirpa
Dmytro Kirpa (dmytrokirpa) merged commit 62aee4c into microsoft:master Sep 3, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants